package model;

import java.util.ArrayList;

import rmi.server.ServerRemote;
import toolkit.Debug;
import exception.GameException;

abstract public class Game
{
	// Maybe not the most beautiful solution, but we need to get a identifier integer for each
	// game and we are not using any database for it.
	private static int counter = 0;
	
	private int id;
	
	protected boolean open = false;
	
	protected ArrayList<User> spectators = new ArrayList<User>();
	
	protected ArrayList<Participant> participants = new ArrayList<Participant>();
	
	protected int seats;

	protected int seatsTaken;
	
	protected int smallBlind;
	
	protected GameVariant gameVariant;
	
	protected GameType gameType;
	
	protected GameStake gameStake;
	
	protected int buyin;
	
	protected ServerRemote serverRemote;
	
	public Game()
	{
		
	}
	
	public Game(GameVariant gameVariant, GameType gameType, GameStake gameStake, int buyin, int seats) throws GameException
	{
		if (seats < 2)
		{
			throw new GameException("A game needs at least two seats to be playable.");
		}
		
		this.id = counter++;
		this.gameVariant = gameVariant;
		this.gameType = gameType;
		this.gameStake = gameStake;
		this.buyin = buyin;
		this.seats = seats;
	}
	
	public void setServerRemote(ServerRemote serverRemote)
	{
		this.serverRemote = serverRemote;
	}
	
	public ServerRemote getServerRemote()
	{
		return serverRemote;
	}
	
	public void setVariant(GameVariant variant)
	{
		this.gameVariant = variant;
	}
	
	public GameVariant getVariant()
	{
		return gameVariant;
	}
	
	public void setType(GameType type)
	{
		this.gameType = type;
	}
	
	public GameType getType()
	{
		return gameType;
	}
	
	public void setStake(GameStake stake)
	{
		this.gameStake = stake;
	}
	
	public GameStake getStake()
	{
		return gameStake;
	}
	
	public int getId()
	{
		return id;
	}
	
	abstract protected int determineSmallBlind();
	
	public int getSmallBlind()
	{
		if (smallBlind == 0)
		{
			int smallBlind = determineSmallBlind();
			
			if (smallBlind < 1)
			{
				smallBlind = 1;
			}
			
			this.smallBlind = smallBlind;
		}
		
		return smallBlind;
	}
	
	public int getBigBlind()
	{
		return getSmallBlind() * 2;
	}
	
	public boolean isOpen()
	{
		return open;
	}
	
	public boolean isOpen(boolean throwException) throws GameException
	{
		if (throwException && !open)
		{
			throw new GameException("The game is not open.");
		}
		
		return open;
	}
	
	abstract protected void openBody();
	
	public void open()
	{
		open = true;
		
		openBody();
	}
	
	abstract protected void startBody() throws GameException;
	
	public void start() throws GameException
	{
		// Can the game start?
		isOpen(true);
		
		if (participants.size() < 2)
		{
			throw new GameException("At least two players need to be joined for the game to start.");
		}
		
		// Stop allowing players.
		open = false;
		
		// Start the game.
		startBody();
	}
	
	public void payout()
	{
		for (Participant participant: participants)
		{
			if (seats == 2)
			{
				if (participant.getEndPosition() == 1)
				{
					participant.getPlayer().updateBalance((buyin * participants.size() * 0.95));
				}
			}
			
			if (seats == 4)
			{
				if (participant.getEndPosition() == 1)
				{
					participant.getPlayer().updateBalance((buyin * participants.size() * 0.95));
				}
			}
			
			if (seats == 6)
			{
				if (participant.getEndPosition() == 1)
				{
					participant.getPlayer().updateBalance((buyin * participants.size() * 0.60));
				}
				
				if (participant.getEndPosition() == 2)
				{
					participant.getPlayer().updateBalance((buyin * participants.size() * 0.35));
				}
			}
			
			if (seats == 8)
			{
				if (participant.getEndPosition() == 1)
				{
					participant.getPlayer().updateBalance((buyin * participants.size() * 0.60));
				}
				
				if (participant.getEndPosition() == 2)
				{
					participant.getPlayer().updateBalance((buyin * participants.size() * 0.35));
				}
			}
			
			if (seats == 10)
			{
				if (participant.getEndPosition() == 1)
				{
					participant.getPlayer().updateBalance((buyin * participants.size() * 0.45));
				}
				
				if (participant.getEndPosition() == 2)
				{
					participant.getPlayer().updateBalance((buyin * participants.size() * 0.30));
				}
				
				if (participant.getEndPosition() == 3)
				{
					participant.getPlayer().updateBalance((buyin * participants.size() * 0.20));
				}
			}
		}
	}
	
	public Participant getParticipant(Player player)
	{
		for (Participant participant: participants)
		{
			if (participant.getPlayer().getId() == player.getId())
			{
				return participant;
			}
		}
		
		return null;
	}
	
	public boolean isPlayerInGame(Player player)
	{
		return getParticipant(player) != null;
	}
	
	abstract public Table getJoinTable() throws GameException;
	
	abstract public boolean shouldStart();
	
	public Participant join(Player player, int seatIndex) throws GameException
	{
		// First we need to check whether the player can join.
		isOpen(true);
		
		if (!Debug.isEnabled() && isPlayerInGame(player))
		{
			throw new GameException("You already joined this game.");
		}
		
		if (!player.checkBalance(buyin))
		{
			throw new GameException("You do not have enough virtual money on your balance.");
		}
		
		// We have established that the player can join the game.
		// So now he can pay the costs.
		payBuyin(player);
		
		// The participant object of the player of this game.
		Participant participant = null;
		
		// Although the player can join, there are still things that can go wrong.
		// If this is the case the player needs to get back is payment.
		try
		{
			if (participants.size() == seats)
			{
				throw new GameException("All seats of the game have been taken.");
			}
			
			//which table should the player join?
			Table table = getJoinTable();
			
			// Make this player a participant to this game.
			participant = new Participant(player);
			
			table.takeSeat(participant, seatIndex);
			participant.setTable(table);
			
			participants.add(participant);
			
			seatsTaken++;
			
			if (shouldStart())
			{
				new GameThread().start();
			}
		}
		catch (GameException e)
		{
			// Something went wrong, so revert the players costs.
			revertBuyin(player);
			
			throw e;
		}
		
		return participant;
	}
	
	public void watch(User user)
	{
		spectators.add(user);
	}
	
	public void leave(User user)
	{
		User spectatorToRemove = null;
		
		for (User spectator: spectators)
		{
			if (spectator.getId() == user.getId())
			{
				spectatorToRemove = spectator;
			}
		}
		
		if (spectatorToRemove != null)
		{
			spectators.remove(spectatorToRemove);
		}
	}
	
	public void leave(Player player)
	{
		leave(getParticipant(player));
	}
	
	public void leave(Participant participant)
	{
		Player player = participant.getPlayer();
		
		participant.getTable().leave(participant);
		
		leave(player);
		
		revertBuyin(player);
		
		seatsTaken--;
	}
	
	public void payBuyin(Player player)
	{
		player.updateBalance(-buyin);
	}

	public void payBuyin(Participant participant)
	{
		payBuyin(participant.getPlayer());
	}

	public void revertBuyin(Player player)
	{
		player.updateBalance(buyin);
	}

	public void revertBuyin(Participant participant)
	{
		payBuyin(participant.getPlayer());
	}
	
	public ArrayList<User> getSpectators()
	{
		return spectators;
	}
	
	public ArrayList<Participant> getParticipants()
	{
		return participants;
	}
	
	public int getSeats()
	{
		return seats;
	}
	
	public void setBuyin(int buyin)
	{
		this.buyin = buyin;
	}
	
	public int getBuyin()
	{
		return buyin;
	}
	
	public int getSeatsTaken()
	{
		return participants.size();
	}
	
	public String toString()
	{
		return gameStake + " " + gameVariant + " - $" + buyin;
	}
	
	private class GameThread extends Thread
	{
		private GameException gameException;
		
		public void run()
		{
			try
			{
				if (!Debug.isTextual())
				{
					Debug.setEnabled(false);
				}
				
				Game.this.start();
				
				if (!Debug.isTextual())
				{
					Debug.setEnabled(true);
				}
			}
			catch (GameException e)
			{
				gameException = e;
			}
		}
		
		public GameException getGameException()
		{
			return gameException;
		}
	};
}
